/*
 * @(#)MethodExecutor.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.core.service;

import static com.nhncorp.usf.core.Constants.*;

import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.nhncorp.usf.core.call.Fault;
import com.nhncorp.usf.core.config.runtime.BusinessObject;
import com.nhncorp.usf.core.config.runtime.Method;
import com.nhncorp.usf.core.config.runtime.ScriptInfo;
import com.nhncorp.usf.core.invoker.ConnectorHolder;
import com.nhncorp.usf.core.invoker.InvocationConnectorManager;
import com.nhncorp.usf.core.invoker.InvocationFuture;
import com.nhncorp.usf.core.service.supplement.PostSupplement;
import com.nhncorp.usf.core.service.supplement.PreSupplement;
import com.nhncorp.usf.core.util.ErrorUtil;
import com.nhncorp.usf.core.xwork.DefaultUsfActionInvocation;

/**
 * @author Web Platform Development Team.
 */
public class MethodExecutor implements Executable {
    Log log = LogFactory.getLog(MethodExecutor.class);

    private PreSupplement[] preSupplements;
    private PostSupplement[] postSupplements;
    private ServiceExecutionContext context;

    /**
     * Instantiates a new method executor.
     *
     * @param context      the context
     * @param preListener  the pre listener
     * @param postListener the post listener
     */
    public MethodExecutor(ServiceExecutionContext context, int preListener, int postListener) {
        this.context = context;
        preSupplements = new PreSupplement[preListener];
        postSupplements = new PostSupplement[postListener];
    }

    /**
     * @param method Method
     * @return MethodReturn
     * @throws ExecutionException the ExecutionException
     * @see com.nhncorp.usf.core.service.Executable#execute(com.nhncorp.usf.core.config.runtime.Method)
     */
    public MethodReturn execute(Method method) throws ExecutionException {
        if (!method.equals(context.getMethod())) {
            context.setMethod(method);
        }

        BusinessObject bo = context.getBo();
        Map<String, Object> methodParameter = context.getMethodParameter();

        if (log.isDebugEnabled()) {
            log.debug("======== execute method information =========");
            log.debug("Action Name : " + context.getAction().getId());
            log.debug("BO Name : " + bo.getId());
            log.debug("Method Name : " + method.getName());
            log.debug("Method Return-ID : " + method.getReturnId());
            log.debug("Use ParameterMap : " + method.getIsParameterMap());
            log.debug("Use Result Cache : " + method.isUseCache());
            log.debug("Parameter Values : " + methodParameter);
            List<ScriptInfo> preScripts = method.getPreScriptList();

            if (preScripts != null) {
                for (ScriptInfo scriptInfo : preScripts) {
                    log.debug("Prescripts : " + scriptInfo.getFunctionName());
                }
            }

            List<ScriptInfo> postScripts = method.getPostScriptList();

            if (postScripts != null) {
                for (ScriptInfo scriptInfo : postScripts) {
                    log.debug("Postscripts : " + scriptInfo.getFunctionName());
                }
            }

            log.debug("=============================================");
        }

        MethodReturn methodReturn = null;

        if (context.getMethodType() == ServiceExecutionContext.MethodType.sequential) {
            ConnectorHolder invoker = InvocationConnectorManager.getInstance().getRBConnectorHolder(bo.getConnectorId());
            try {
                InvocationFuture future = invoker.invoke(bo.getId(), method.getName(), methodParameter);
                methodReturn = new MethodReturn(future.await(), context);
            } catch (Exception e) {
                ExecutionException exception = new ExecutionException(e);
                exception.setTargetName(context.getAction().getId() + "/" + context.getBo().getId() + "/"
                        + context.getMethod().getName());
                throw exception;
            }
        } else {

            ConnectorHolder invoker = InvocationConnectorManager.getInstance().getRBConnectorHolder(bo.getConnectorId());
            try {
                InvocationFuture future = invoker.invoke(bo.getId(), method.getName(), methodParameter);

                //			AsyncResult asyncResult = new AsyncResult(action, bo,
                //					method, future, methodParameter);
                methodReturn = new MethodReturn(future, context);

                //			futuresMap.put(method.getReturnId(), asyncResult);
            } catch (Exception e) {
                ExecutionException exception = new ExecutionException(e);
                exception.setTargetName(context.getAction().getId() + "/" + context.getBo().getId() + "/"
                        + context.getMethod().getName());
                throw exception;
            }
        }

        return methodReturn;
    }

    /**
     * Failure.
     *
     * @param fault   the fault
     * @param context the context
     * @throws Exception the exception
     */
    @SuppressWarnings("unchecked")
    public void failure(Fault fault, ServiceExecutionContext context) throws Exception {
        Map<String, String> exceptionData = (Map<String, String>) fault.getDetail();
        context.setAttribute(EXCEPTION_DATA, exceptionData);
        String exceptionClassName = ErrorUtil.getProperties(fault.getMessage()).get("classname");
        context.setAttribute(DefaultUsfActionInvocation.KEY_FAILURE_CODE, exceptionClassName);
        context.getExceptionHandler().handle(fault, context);
    }

    /**
     * Adds the pre supplement.
     *
     * @param preSupplement the pre supplement
     */
    public void addPreSupplement(PreSupplement preSupplement) {
        if (preSupplement == null) {
            return;
        }
        for (int i = 0; i < preSupplements.length; i++) {
            if (preSupplements[i] == null) {
                preSupplements[i] = preSupplement;
                return;
            }
        }

    }

    /**
     * Adds the post supplement.
     *
     * @param postSupplement the post supplement
     */
    public void addPostSupplement(PostSupplement postSupplement) {
        if (postSupplement == null) {
            return;
        }
        for (int i = 0; i < postSupplements.length; i++) {
            if (postSupplements[i] == null) {
                postSupplements[i] = postSupplement;
                return;
            }
        }
    }

    /**
     * @return boolean
     * @throws ExecutionException the ExecutionException
     * @see com.nhncorp.usf.core.service.Executable#after()
     */
    public boolean after() throws ExecutionException {
        boolean postResult = true;

        for (PostSupplement post : postSupplements) {
            if (post == null) {
                continue;
            }

            log.debug("postSupplement works. " + post.getClass() + " return true.");

            if (!post.afterExecute(context)) {
                log.debug("postSupplement works. " + post.getClass() + " return false.");
                postResult = false;
            }
        }

        return postResult;
    }

    /**
     * @return boolean
     * @throws ExecutionException the ExecutionException
     * @see com.nhncorp.usf.core.service.Executable#before()
     */
    public boolean before() throws ExecutionException {
        boolean preResult = true;

        for (PreSupplement pre : preSupplements) {
            if (pre == null) {
                continue;
            }

            log.debug("preSupplement works. " + pre.getClass() + " return true.");

            if (!pre.beforeExecute(context)) {
                log.debug("preSupplement works. " + pre.getClass() + " return false.");
                preResult = false;
            }
        }

        return preResult;
    }

    /**
     * Gets the context.
     *
     * @return the context
     */
    public ServiceExecutionContext getContext() {
        return this.context;
    }

}
